// Copyright 2012 Google Inc. All Rights Reserved.

package com.google.appengine.preferencessync.internal.merge;

import com.google.appengine.preferencessync.MergeHandler;
import com.google.appengine.preferencessync.internal.InternalSyncException;
import com.google.appengine.preferencessync.internal.InternalSyncException.ExceptionType;
import com.google.appengine.preferencessync.internal.SharedPreferencesUtils;
import com.google.appengine.preferencessync.internal.SyncClientLog;
import com.google.appengine.syncserver.shared.PropertyChange;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.util.Log;

import java.util.ArrayList;

/**
 * Custom merge handler which calls users merge handler.
 */
public class CustomWins implements InternalMergeHandler {
  private static final String MERGE_SERVICE_INTENT = "com.google.appengine.preferencessync.merge";

  private final Context context;
  private final String preferencesName;
  private final Object waitObject = new Object();
  private final BindHandler bindHandler;
  private static final int WAIT_TIME = 100 * 1000; // 100 secs.

  public CustomWins(Context context, String preferences) {
    this.context = context;
    this.preferencesName = preferences;
    this.bindHandler = new BindHandler();
  }

  /**
   * Handles bindService().
   */
  private class BindHandler implements ServiceConnection {
    private MergeHandler mergeHandler = null;

    /**
     * Merge handler.
     */
    public BindHandler() {
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
      SyncClientLog.logDebug(this.getClass(), "Assigning merge handler");
      mergeHandler = (MergeHandler) binder;
      synchronized (waitObject) {
        waitObject.notify();
      }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
      mergeHandler = null;
    }

    public MergeHandler getMergeHandler() {
      return mergeHandler;
    }
  }

  /**
   * Inherited from the {@link InternalMergeHandler}
   */
  @Override
  public void merge(SharedPreferences clientPreferences, ArrayList<PropertyChange> clientChanges,
      ArrayList<PropertyChange> propertyChanges) throws InternalSyncException {
    MergeHandler mergeHandler = getMergeHandler(context);
    SyncClientLog.logDebug(this.getClass(), "Using merge handler");
    if (mergeHandler != null) {
      callMergeHandler(propertyChanges, mergeHandler);
    } else {
      SyncClientLog.logDebug(this.getClass(), "Failed to obtain the custom merge handler");
    }
  }

  /**
   * Get merge handler by calling the service. The service in turn returns the merge handler.
   *
   * @param context
   * @throws InternalSyncException
   */
  private MergeHandler getMergeHandler(Context context) throws InternalSyncException {
    MergeHandler mergeHandler = bindHandler.getMergeHandler();
    if (mergeHandler == null) {
      Intent intent = new Intent(MERGE_SERVICE_INTENT);
      // Run intents only on package the library is running.
      intent.setPackage(context.getPackageName());
      synchronized (waitObject) {
        if (context.bindService(intent, bindHandler, Context.BIND_AUTO_CREATE)) {
          mergeHandler = bindHandler.getMergeHandler();
          try {
            waitObject.wait(WAIT_TIME);
          } catch (InterruptedException e) {
            throw new InternalSyncException(
                ExceptionType.INTERNAL_EXCEPTION, e.getMessage(), Log.ERROR);
          }
        }
      }
    }
    return mergeHandler;
  }

  private void callMergeHandler(ArrayList<PropertyChange> propertyChanges,
      MergeHandler mergeHandler) throws InternalSyncException {
    mergeHandler.onMergeStarted(preferencesName);
    // Iterate over all property changes on the server and corresponding handler functions in the
    // client mergeHandler.
    for (PropertyChange change : propertyChanges) {
      switch(change.getType()) {
        case DELETE:
          mergeHandler.onDelete(change.getKey());
          break;
        case UPDATE:
          mergeHandler.onUpdate(change.getKey(),
              SharedPreferencesUtils.getNamespacedStringAsObject(change.getValue()));
          break;
      }
    }
    mergeHandler.onMergeFinished();
  }
}
